﻿using System.Linq;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;

namespace OpenRA.Mods.Common.Traits
{
	[Desc("TA Special. Used with CircleSelectionDecorations. This actor is selectable. Defines bounds of selectable area, selection class, selection priority and selection priority modifiers.")]
	public class UnitIsoSelectableInfo : TraitInfo, IMouseBoundsInfo, ISelectableInfo, IRulesetLoaded
	{
		[Desc("Defines a custom rectangle for mouse interaction with the actor.",
			"The first two numbers define the width and depth of the footprint rectangle.",
			"The (optional) second two numbers define an x and y offset from the actor center.")]
		public readonly WDist[] Bounds = { WDist.Zero, WDist.Zero };

		[Desc("Height above the footprint for the top of the interaction rectangle.")]
		public readonly WDist Height = new WDist(1024);

		[Desc("Defines a custom rectangle for Decorations (e.g. the selection box).",
	"If null, Bounds will be used instead")]
		public readonly int[] DecorationBounds = null;

		public readonly int Priority = 10;

		[Desc("Allow selection priority to be modified using a hotkey.",
			"Valid values are None (priority is not affected by modifiers)",
			"Ctrl (priority is raised when Ctrl pressed) and",
			"Alt (priority is raised when Alt pressed).")]
		public readonly SelectionPriorityModifiers PriorityModifiers = SelectionPriorityModifiers.None;

		[Desc("All units having the same selection class specified will be selected with select-by-type commands (e.g. double-click). ",
			"Defaults to the actor name when not defined or inherited.")]
		public readonly string Class = null;

		[VoiceReference]
		public readonly string Voice = "Select";

		public override object Create(ActorInitializer init) { return new TAUnitSelectable(init.Self, this); }

		int ISelectableInfo.Priority { get { return Priority; } }
		SelectionPriorityModifiers ISelectableInfo.PriorityModifiers { get { return PriorityModifiers; } }
		string ISelectableInfo.Voice { get { return Voice; } }

		public virtual void RulesetLoaded(Ruleset rules, ActorInfo ai)
		{
			var grid = Game.ModData.Manifest.Get<MapGrid>();
			if (grid.Type != MapGridType.RectangularIsometric)
				throw new YamlException("UnitIsoSelectable can only be used in mods that use the RectangularIsometric MapGrid type.");
		}
	}

	public class TAUnitSelectable : IMouseBounds, ISelectable, INotifyCreated
	{
		readonly UnitIsoSelectableInfo info;
		readonly string selectionClass = null;
		protected IFacing facing;
		IAutoMouseBounds[] autoBounds;

		public TAUnitSelectable(Actor self, UnitIsoSelectableInfo info)
		{
			this.info = info;
			selectionClass = string.IsNullOrEmpty(info.Class) ? self.Info.Name : info.Class;
		}

		void INotifyCreated.Created(Actor self)
		{
			autoBounds = self.TraitsImplementing<IAutoMouseBounds>().ToArray();
			facing = self.TraitOrDefault<IFacing>();
		}

		Polygon Bounds(Actor self, WorldRenderer wr, WDist[] bounds, WDist height)
		{
			int2 left, right, top, bottom;

			var offset = bounds.Length >= 4 ? new WVec(bounds[2], bounds[3], WDist.Zero).Rotate(WRot.FromYaw(facing.Facing)) : WVec.Zero; // 正常的isometric视角选框是new WAngle(-128)
			var center = self.CenterPosition + offset;

			if (facing.Facing.Angle > 768)
			{
				left = wr.ScreenPxPosition(center + new WVec(-bounds[0], bounds[1], WDist.Zero).Rotate(WRot.FromYaw(new WAngle(facing.Facing.Angle))));
				right = wr.ScreenPxPosition(center + new WVec(bounds[0], -bounds[1], WDist.Zero).Rotate(WRot.FromYaw(new WAngle(facing.Facing.Angle))));
				top = wr.ScreenPxPosition(center + new WVec(-bounds[0], -bounds[1], WDist.Zero).Rotate(WRot.FromYaw(new WAngle(facing.Facing.Angle))));
				bottom = wr.ScreenPxPosition(center + new WVec(bounds[0], bounds[1], WDist.Zero).Rotate(WRot.FromYaw(new WAngle(facing.Facing.Angle))));
			}
			else if (facing.Facing.Angle > 512)
			{
				left = wr.ScreenPxPosition(center + new WVec(-bounds[1], bounds[0], WDist.Zero).Rotate(WRot.FromYaw(new WAngle(facing.Facing.Angle + 256))));
				right = wr.ScreenPxPosition(center + new WVec(bounds[1], -bounds[0], WDist.Zero).Rotate(WRot.FromYaw(new WAngle(facing.Facing.Angle + 256))));
				top = wr.ScreenPxPosition(center + new WVec(-bounds[1], -bounds[0], WDist.Zero).Rotate(WRot.FromYaw(new WAngle(facing.Facing.Angle + 256))));
				bottom = wr.ScreenPxPosition(center + new WVec(bounds[1], bounds[0], WDist.Zero).Rotate(WRot.FromYaw(new WAngle(facing.Facing.Angle + 256))));
			}
			else if (facing.Facing.Angle > 256)
			{
				left = wr.ScreenPxPosition(center + new WVec(-bounds[0], bounds[1], WDist.Zero).Rotate(WRot.FromYaw(new WAngle(facing.Facing.Angle + 512))));
				right = wr.ScreenPxPosition(center + new WVec(bounds[0], -bounds[1], WDist.Zero).Rotate(WRot.FromYaw(new WAngle(facing.Facing.Angle + 512))));
				top = wr.ScreenPxPosition(center + new WVec(-bounds[0], -bounds[1], WDist.Zero).Rotate(WRot.FromYaw(new WAngle(facing.Facing.Angle + 512))));
				bottom = wr.ScreenPxPosition(center + new WVec(bounds[0], bounds[1], WDist.Zero).Rotate(WRot.FromYaw(new WAngle(facing.Facing.Angle + 512))));
			}
			else
			{
				left = wr.ScreenPxPosition(center + new WVec(-bounds[1], bounds[0], WDist.Zero).Rotate(WRot.FromYaw(new WAngle(facing.Facing.Angle + 768))));
				right = wr.ScreenPxPosition(center + new WVec(bounds[1], -bounds[0], WDist.Zero).Rotate(WRot.FromYaw(new WAngle(facing.Facing.Angle + 768))));
				top = wr.ScreenPxPosition(center + new WVec(-bounds[1], -bounds[0], WDist.Zero).Rotate(WRot.FromYaw(new WAngle(facing.Facing.Angle + 768))));
				bottom = wr.ScreenPxPosition(center + new WVec(bounds[1], bounds[0], WDist.Zero).Rotate(WRot.FromYaw(new WAngle(facing.Facing.Angle + 768))));
			}

			if (height.Length == 0)
				return new Polygon(new[] { top, left, bottom, right });

			var h = new int2(0, (int)((float)(height.Length) * wr.TileSize.Height / wr.TileScale));
			return new Polygon(new[] { top - h, left - h, left, bottom, right, right - h });
		}

		public Polygon Bounds(Actor self, WorldRenderer wr)
		{
			return Bounds(self, wr, info.Bounds, info.Height);
		}

		Rectangle AutoBounds(Actor self, WorldRenderer wr)
		{
			return autoBounds.Select(s => s.AutoMouseoverBounds(self, wr)).FirstOrDefault(r => !r.IsEmpty);
		}

		Polygon BoundsDec(Actor self, WorldRenderer wr, int[] bounds)
		{
			if (bounds == null)
				return new Polygon(AutoBounds(self, wr));

			var size = new int2(bounds[0], bounds[1]);

			var offset = -size / 2;
			if (bounds.Length > 2)
				offset += new int2(bounds[2], bounds[3]);

			var xy = wr.ScreenPxPosition(self.CenterPosition) + offset;
			return new Polygon(new Rectangle(xy.X, xy.Y, size.X, size.Y));
		}

		public Rectangle DecorationBounds(Actor self, WorldRenderer wr)
		{
			return BoundsDec(self, wr, info.DecorationBounds).BoundingRect;
		}

		Polygon IMouseBounds.MouseoverBounds(Actor self, WorldRenderer wr)
		{
			return Bounds(self, wr);
		}

		string ISelectable.Class { get { return selectionClass; } }
	}
}
